/*
 * ULSF Sagittarius IDA
 * Copyright (C) 2012, John Pritchard, all rights reserved.
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
package sagittarius.design;

import sagittarius.Debug;
import sagittarius.Field;
import sagittarius.Values;
import sagittarius.Visualization;

import sagittarius.airfoil.Airfoil;

import json.Json;

import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Plane;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;


/**
 * This class is instantiated for each user input parameters set to
 * produce a triangle mesh for the configured {@link Volume}.
 * 
 * This {@link sagittarius.Visualization Visualization} operator
 * maintains user input parameters and performs a triangulation of
 * {@link Section} data asynchronously via the {@link Generator}
 * class.
 * 
 * @see Visualization
 * @see Volume
 * @see Axes
 */
public class Body
    extends fv3.math.Abstract
    implements Visualization
{


    public final float length, radius, span;
    public final float alpha, beta, gamma, delta, epsilon;
    public final float zeta, eta, theta, iota, kappa;

    private Mesh mesh, airfoilMesh;

    private float[] vertices;

    private int count;

    private Visualization former;

    private Generator generator;

    private String string;

    private final Object monitor = new Object();


    public Body(){
        this(null,Field.Length.def,Field.Radius.def,Field.Span.def,
             Field.Alpha.def,Field.Beta.def,Field.Gamma.def,Field.Delta.def,Field.Epsilon.def,
             Field.Zeta.def,Field.Eta.def,Field.Theta.def,Field.Iota.def,Field.Kappa.def);
    }
    public Body(Json json){
        this(null, Field.Decode(json));
    }
    public Body(Visualization copy, Json json){
        this(copy, Field.Decode(json));
    }
    public Body(Visualization copy, Field update, float value){
        this(copy, Field.Parameters(copy,update,value));
    }
    public Body(Visualization copy, Values set){
        this(copy, Field.Sets(copy,set));
    }
    private Body(Visualization copy, float[] parameters){
        this(copy, 
             parameters[Field.Length.ordinal()],
             parameters[Field.Radius.ordinal()],
             parameters[Field.Span.ordinal()],
             parameters[Field.Alpha.ordinal()],
             parameters[Field.Beta.ordinal()],
             parameters[Field.Gamma.ordinal()],
             parameters[Field.Delta.ordinal()],
             parameters[Field.Epsilon.ordinal()],
             parameters[Field.Zeta.ordinal()],
             parameters[Field.Eta.ordinal()],
             parameters[Field.Theta.ordinal()],
             parameters[Field.Iota.ordinal()],
             parameters[Field.Kappa.ordinal()]);
    }
    private Body(Visualization copy, float length, float radius, float span, 
                 float alpha, float beta, float gamma, float delta, float epsilon, 
                 float zeta, float eta, float theta, float iota, float kappa)
    {
        super();
        this.length = length;
        this.radius = radius;
        this.span = span;
        this.alpha = alpha;
        this.beta = beta;
        this.gamma = gamma;
        this.delta = delta;
        this.epsilon = epsilon;
        this.zeta = zeta;
        this.eta = eta;
        this.theta = theta;
        this.iota = iota;
        this.kappa = kappa;
        this.former = copy;
        /*
         * Avoid regeneration on redundant data input
         */
        if (null == copy || (!this.equals(copy)))
            this.generator = new Generator(this);
    }



    public float get(Field field){
        switch(field){
        case Length:
            return this.length;
        case Radius:
            return this.radius;
        case Span:
            return this.span;
        case Alpha:
            return this.alpha;
        case Beta:
            return this.beta;
        case Gamma:
            return this.gamma;
        case Delta:
            return this.delta;
        case Epsilon:
            return this.epsilon;
        case Zeta:
            return this.zeta;
        case Eta:
            return this.eta;
        case Theta:
            return this.theta;
        case Iota:
            return this.iota;
        case Kappa:
            return this.kappa;
        default:
            throw new Error(field.name());
        }
    }
    public float getLength(){
        return this.length;
    }
    public float getRadius(){
        return this.radius;
    }
    public float getSpan(){
        return this.span;
    }
    public float getAlpha(){
        return this.alpha;
    }
    public float getBeta(){
        return this.beta;
    }
    public float getGamma(){
        return this.gamma;
    }
    public float getDelta(){
        return this.delta;
    }
    public float getEpsilon(){
        return this.epsilon;
    }
    public float getZeta(){
        return this.zeta;
    }
    public float getEta(){
        return this.eta;
    }
    public float getTheta(){
        return this.theta;
    }
    public float getIota(){
        return this.iota;
    }
    public float getKappa(){
        return this.kappa;
    }
    public String getVolume(){
        return Volume.Configuration();
    }
    public String getAirfoilConfiguration(){
        return Airfoil.Configuration();
    }
    public String getAirfoilIdentifier(){
        return Airfoil.Identifier();
    }
    public int getCount(){
        return this.count;
    }
    public Mesh getAirfoilMesh(){
        Mesh mesh = this.airfoilMesh;
        if (null == mesh){
            final Airfoil airfoil = Airfoil.Constructor(this);
            airfoil.init(60);
            mesh = airfoil.getMesh();
            this.airfoilMesh = mesh;
        }
        return mesh;
    }
    public Mesh getMesh(){
        Mesh mesh = this.mesh;
        if (null == mesh){
            float[] vertices = this.vertices;
            int count = this.count;
            if (0 < count && null != vertices){

                this.disposeFormer();

                this.mesh = new Mesh(Visualization.Static,this.count,0,
                                     VertexAttribute.Position(),
                                     VertexAttribute.Normal());

                this.mesh.setVertices(vertices);
            }
            else {
                Visualization former = this.former;
                if (null != former)
                    return former.getMesh();
            }
        }
        return mesh;
    }
    public void dispose(){
        Generator generator = this.generator;
        if (null != generator){
            this.generator = null;
            generator.stop();
        }
        Mesh mesh = this.mesh;
        if (null != mesh){
            this.mesh = null;
            this.count = 0;
            mesh.dispose();
        }
        Visualization former = this.former;
        if (null != former){
            this.former = null;
            former.dispose();
        }
    }
    private void disposeFormer(){
        Visualization former = this.former;
        if (null != former){
            this.former = null;
            former.dispose();
        }
    }
    /**
     * Triangulation operator constructs faces and mesh from {@link
     * Volume} {@link Section Sections}.  The mesh vertex set has
     * order (vertex, normal)+, (vX,vY,vZ,nX,nY,nZ)+.
     * 
     * @see Generator
     */
    public void generate(){
        float[] vertices = null;

        final Volume volume = Volume.Constructor(this);
        /*
         * Center at (0,0,0)
         */
        final float r = this.radius;
        final float l = this.length/2.0f;
        final float s = this.span/2.0f;

        if (Debug.Geometry)
            System.out.printf("[G/B/0] length % 4.2f, half-length % 4.2f%n",this.length,l);

        /*
         * Delta longitude employed for abstract and section
         * coordinates
         */
        final float dl = l/20f;

        final float head = (l-dl);
        final float tail = (-l+dl);
        /*
         * Abstract position of section
         * 
         * (0.0 < station < length)
         */
        float station;
        /*
         * Coordinate position of section
         * 
         * (head >= longitude >= tail)
         */
        float longitude;
        /*
         * Longitudinal section from volume constraint operator
         */
        Section section;
        /*
         * Section previous (X,Y,Z)+ path vertices
         */
        float[] sp;
        /*
         * Section next (X,Y,Z)+ path vertices
         */
        float[] sn;
        /*
         * Vertex previous index
         */
        int sc;
        int scX, scY, scZ;
        /*
         * Vertex next index
         */
        int sd;
        int sdX, sdY, sdZ;
        /*
         * Vertex lists length
         */
        int snlen, splen;
        /*
         * Initialize airfoil model
         */
        volume.airfoils(this,60);
        /*
         * Head (X+)
         */
        station = dl;
        longitude = head;

        section = volume.stationForX(station,longitude);

        sp = section.getVertices3(longitude);
        sn = sp;

        splen = sp.length;
        snlen = sn.length;

        if (Debug.Geometry)
            System.out.printf("[G/B/H] station % 4.2f, longitude % 4.2f, sector % 4.2f, area % 6.2f%n",station,longitude,section.getHyperbolicSector(),section.getArea());

        for (sc = 0; sc < snlen; sc += 3){

            sd = (sc+3);

            if (sd >= snlen)
                sd = 0;

            scX = (sc+X);
            scY = (sc+Y);
            scZ = (sc+Z);

            sdX = (sd+X);
            sdY = (sd+Y);
            sdZ = (sd+Z);

            if (Debug.Triangulation){
                System.out.printf("[T/B/H] sn.length % 4d, sc % 4d, sd % 4d%n",sn.length,sc,sd);
                System.out.printf("        face {%n");
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",+l, 0.0f, 0.0f);
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[sdX],sn[sdY],sn[sdZ]);
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[scX],sn[scY],sn[scZ]);
                System.out.printf("        }%n");
            }

            float[] face = new float[]{
               +l, 0, 0, // V
                0, 0, 0, // N
                sn[sdX],sn[sdY],sn[sdZ],
                0, 0, 0, // N
                sn[scX],sn[scY],sn[scZ],
                0, 0, 0  // N
            };

            vertices = Cat(vertices,face);
        };

        sp = sn;
        /*
         * Intermediate (X+ > 0.0 > X-)
         */
        for (; longitude > tail; ){
            station += dl;
            longitude -= dl;

            section = volume.stationForX(station,longitude);

            sn = section.getVertices3(longitude);

            splen = sp.length;
            snlen = sn.length;

            if (Debug.Geometry)
                System.out.printf("[G/B/B] station % 4.2f, longitude % 4.2f, sector % 4.2f, area % 6.2f%n",station,longitude,section.getHyperbolicSector(),section.getArea());

            if (Debug.Triangulation)
                System.out.printf("[T/B/I/0] sn.length % 4d, sp.length % 4d%n",sn.length,sp.length);

            for (sc = 0; sc < snlen; sc += 3){

                sd = (sc+3);

                if (sd >= snlen)
                    sd = 0;

                scX = (sc+X);
                scY = (sc+Y);
                scZ = (sc+Z);

                sdX = (sd+X);
                sdY = (sd+Y);
                sdZ = (sd+Z);

                if (Debug.Triangulation){
                    System.out.printf("[T/B/I/1] sn.length % 4d, sd % 4d, sp.length % 4d, sc % 4d%n",sn.length,sd,sp.length,sc);
                    if (0 == sc || 0 == sd){
                        System.out.printf("        face {%n");
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sp[scX],sp[scY],sp[scZ]);
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[sdX],sn[sdY],sn[sdZ]);
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[scX],sn[scY],sn[scZ]);
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sp[scX],sp[scY],sp[scZ]);
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sp[sdX],sp[sdY],sp[sdZ]);
                        System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[sdX],sn[sdY],sn[sdZ]);
                        System.out.printf("        }%n");
                    }
                }

                float[] faces = new float[]{
                    sp[scX],sp[scY],sp[scZ],
                    0, 0, 0, // N
                    sn[sdX],sn[sdY],sn[sdZ],
                    0, 0, 0, // N
                    sn[scX],sn[scY],sn[scZ],
                    0, 0, 0, // N
                    sp[scX],sp[scY],sp[scZ],
                    0, 0, 0, // N
                    sp[sdX],sp[sdY],sp[sdZ],
                    0, 0, 0, // N
                    sn[sdX],sn[sdY],sn[sdZ],
                    0, 0, 0  // N
                };

                vertices = Cat(vertices,faces);
            };
            sp = sn;
        }
        /*
         * Tail (X-)
         */

        if (Debug.Geometry)
            System.out.printf("[G/B/T] station % 4.2f, longitude % 4.2f, sector % 4.2f, area % 6.2f%n",station,longitude,section.getHyperbolicSector(),section.getArea());

        for (sc = 0; sc < snlen; sc += 3){

            sd = (sc+3);

            if (sd >= snlen)
                sd = 0;

            scX = (sc+X);
            scY = (sc+Y);
            scZ = (sc+Z);

            sdX = (sd+X);
            sdY = (sd+Y);
            sdZ = (sd+Z);

            if (Debug.Triangulation){
                System.out.printf("[T/B/T] sn.length % 4d, sc % 4d, sd % 4d%n",sn.length,sc,sd);
                System.out.printf("        face {%n");
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",-l, 0.0f, 0.0f);
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[scX],sn[scY],sn[scZ]);
                System.out.printf("           % 4.4f, % 4.4f, % 4.4f,%n",sn[sdX],sn[sdY],sn[sdZ]);
                System.out.printf("        }%n");
            }

            float[] face = new float[]{
               -l, 0, 0, // V
                0, 0, 0, // N
                sn[scX],sn[scY],sn[scZ],
                0, 0, 0, // N
                sn[sdX],sn[sdY],sn[sdZ],
                0, 0, 0  // N
            };

            vertices = Cat(vertices,face);
        };


        Arrays.Scale(vertices,sagittarius.Controller.Scale(r,l,s));

        Arrays.FillNormals(vertices);

        /*
         * Define field count before field vertices for 'getMesh'
         */
        this.count = vertices.length/(3*2);

        this.vertices = vertices;

        if (Debug.Mesh)
            this.list();
    }
    public void terminal(){

        synchronized(this.monitor){
            this.monitor.notifyAll();
        }
    }
    public boolean waitfor()
        throws InterruptedException
    {
        synchronized(this.monitor){
            this.monitor.wait();
        }
        return (null != this.vertices && 0 < this.count);
    }
    public void list(){

        if (Debug.Mesh){
            System.out.println("#");
            System.out.println("# Body Mesh");
            System.out.println("#");
            System.out.println("# [vertex_index] (a_position)/(a_normal)");
            System.out.println("#");

            final float[] vertices = this.vertices;
            final int verticesCount = this.count;

            float[] a_position = new float[3];
            float[] a_normal = new float[3];

            for (int vv = 0, ofs; vv < verticesCount; vv++){
                ofs = (vv*(3*2));
                System.arraycopy(vertices,ofs,a_position,0,3);
                ofs += 3;
                System.arraycopy(vertices,ofs,a_normal,0,3);

                System.out.printf("[%d] (% 4.4f,% 4.4f,% 4.4f)/(% 4.4f,% 4.4f,% 4.4f)%n",vv,a_position[0],a_position[1],a_position[2],a_normal[0],a_normal[1],a_normal[2]);
            }
        }
    }
    public String toString(){
        String string = this.string;
        if (null == string){
            StringBuilder strbuf = new StringBuilder();
            strbuf.append('(');
            strbuf.append(this.length);
            strbuf.append(',');
            strbuf.append(this.radius);
            strbuf.append(',');
            strbuf.append(this.span);
            strbuf.append(')');
            string = strbuf.toString();
            this.string = string;
        }
        return string;
    }
    public boolean equals(Object that){
        if (that instanceof Body)
            return this.equals((Body)that);
        else
            return false;
    }
    public boolean equals(Body that){
        return (this.length == that.length && this.radius == that.radius && this.span == that.span
                && this.alpha == that.alpha && this.beta == that.beta && this.gamma == that.gamma
                && this.delta == that.delta && this.epsilon == that.epsilon
                && this.zeta == that.zeta && this.eta == that.eta && this.theta == that.theta
                && this.iota == that.iota && this.kappa == that.kappa);
    }
    public Json toJson(){

        return Field.Encode(this);
    }
    public boolean fromJson(Json thisModel){

        throw new json.Builder.Immutable();
    }
}
